package com.yueyang.sojourn.server.common.baymax.web.resolver;

import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * 将所有异常转换为错误信息输出，会将业务码放在
 * 
 * @author huisman
 * @since 1.0.0
 * @Copyright (c) 2015, yueyang Group All Rights Reserved.
 */
public abstract class AbstractResultExceptionResolver extends AbstractHandlerExceptionResolver {

  private List<HttpMessageConverter<?>> messageConverters =
      new ArrayList<>(new BuiltinRestHttpMessageConverters().get());

  private RemoteIpResolver remoteIpResolver = new RemoteIpResolver();

  public AbstractResultExceptionResolver() {
    super();
    this.messageConverters = new BuiltinRestHttpMessageConverters().get();
  }


  public AbstractResultExceptionResolver(
      List<HttpMessageConverter<?>> additionalMessageConverters) {
    super();
    this.messageConverters =
        new BuiltinRestHttpMessageConverters(additionalMessageConverters).get();
  }

  static class RemoteIpResolver {
    private static final String[] OPTIONAL_IP_INDICATE_HEADERS =
        new String[] {"X-Real-IP ", "X-Client-Id", "X-Forwarded-For"};

    /**
     * 获取客户端IP
     */
    String getClientIp(HttpServletRequest request) {
      String ip = null;
      for (String ipHeader : OPTIONAL_IP_INDICATE_HEADERS) {
        ip = request.getHeader(ipHeader);
        if (ip == null || ip.trim().isEmpty() || "unknown".equalsIgnoreCase(ip)) {
          // 可选头没找到
          continue;
        }
        break;
      }

      // still not found
      if (ip == null) {
        return request.getRemoteAddr();
      } else {
        int firstCommaIndex = -1;
        if ((firstCommaIndex = ip.indexOf(',')) > 0) {
          // 如果经过多个节点转发，IP地址可能有多个，取第一个
          return ip.substring(0, firstCommaIndex);
        }
        return ip;
      }
    }
  }

  @Override
  protected String buildLogMessage(Exception ex, HttpServletRequest request) {
    if (request == null) {
      return super.buildLogMessage(ex, request);
    }
    return formatLogMessage(false, ex, request);
  }

  final protected String formatLogMessage(boolean logExceptionClassAndMessage, Exception ex,
      HttpServletRequest request) {
    StringBuilder sb = new StringBuilder(220);
    sb.append("method:" + request.getMethod()).append(",path:" + request.getRequestURI())
        .append(",query:" + request.getQueryString() + ",remoteIp:"
            + this.remoteIpResolver.getClientIp(request));
    if (logExceptionClassAndMessage) {
      sb.append(",Exception:" + ex.getClass().getName() + "(" + ex.getMessage() + ")");
    }
    return sb.toString();
  }

  /**
   * 如果返回null,可以继续交给下一个ExceptionResolver处理
   */
  @Override
  protected ModelAndView doResolveException(HttpServletRequest request,
      HttpServletResponse response, Object handler, Exception ex) {
    try {
      if (shouldResolveException(request, response, handler, ex)) {
        return writeResponseMessage(request, response, handler, ex);
      }
    } catch (Exception resolveEx) {
      logger.warn("error occured when handle  exception, context :"
          + formatLogMessage(false, resolveEx, request), resolveEx);
    }
    return null;
  }

  /**
   * 是否应该解析这个异常信息 如果可以解析，则子类必须实现
   * {@link #resolveResponseMessage(HttpServletRequest, HttpServletResponse, Object, Exception)}
   * 
   * @since: 1.0.10
   * @param request
   * @param response
   * @param handler
   * @param ex
   */
  protected abstract boolean shouldResolveException(HttpServletRequest request,
      HttpServletResponse response, Object handler, Exception ex);

  /**
   * 解析我们应该返回的HttpStatus以及json body数据
   * 
   * @since: 1.0.10
   * @param request
   * @param response
   * @param handler
   * @param ex
   */
  protected abstract com.yueyang.sojourn.server.common.baymax.web.resolver.RestResponseMessage resolveResponseMessage(HttpServletRequest request,
                                                                                                    HttpServletResponse response, Object handler, Exception ex);

  /**
   * @since 1.0.0
   * @param request
   * @param response
   * @param handler handlerMethod
   * @param ex handler异常
   * @throws Exception
   */
  private final ModelAndView writeResponseMessage(HttpServletRequest request,
      HttpServletResponse response, Object handler, Exception ex) throws Exception {
    RestResponseMessage headerMessage = resolveResponseMessage(request, response, handler, ex);
    response.setStatus(headerMessage.getHttpStatus());
    // 添加X-Error-Code
    response.setHeader(BizCodes.HEADERS_X_ERROR_CODE, String.valueOf(headerMessage.getCode()));
    writeMessage(new ResultMessage(headerMessage.getCode(), headerMessage.getMessage()), request,
        response);
    return new ModelAndView();
  }

  @SuppressWarnings({"unchecked", "rawtypes", "resource"})
  private final void writeMessage(ResultMessage returnVal, HttpServletRequest request,
      HttpServletResponse response) throws Exception {
    // 设置请求路径
    returnVal.setTimestamp(System.currentTimeMillis());
    returnVal.setPath(request.getRequestURI());
    ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
    Class<?> returnValueClass = returnVal.getClass();
    MediaType jsonType = MediaType.APPLICATION_JSON;
    for (HttpMessageConverter messageConverter : this.messageConverters) {
      if (messageConverter.canWrite(returnValueClass, jsonType)) {
        messageConverter.write(returnVal, jsonType, outputMessage);
        return;
      }
    }
  }


  /**
   * 响应消息<br>
   * 指定httpStatus响应吗,另外生成body信息
   * 
   * @author huisman
   * @since 1.0.10
   * @Copyright (c) 2015,Youzhixu.com Rights Reserved.
   */
  protected static class ResponseMessage {
    /**
     * http响应码
     */
    private int httpStatusCode;
    /**
     * http 响应Body
     */
    private ResultMessage result;

    public ResponseMessage(int httpStatusCode, int resultCode, String message) {
      super();
      this.httpStatusCode = httpStatusCode;
      this.result = new ResultMessage(resultCode, message);
    }

    public int getHttpStatusCode() {
      return httpStatusCode;
    }

    public ResultMessage getResult() {
      return result;
    }

    @Override
    public String toString() {
      return "ResponseMessage [httpStatusCode=" + httpStatusCode + ", result=" + result + "]";
    }
  }

  /**
   * 标准错误json body信息
   * 
   * @author huisman
   * @since 1.0.10
   * @Copyright (c) 2015,Youzhixu.com Rights Reserved.
   */
  static class ResultMessage implements Serializable {
    private static final long serialVersionUID = 1L;
    private String message;
    private int code;
    private String path;
    private long timestamp;
    private boolean success = false;


    public void setTimestamp(long timestamp) {
      this.timestamp = timestamp;
    }

    public String getPath() {
      return path;
    }

    public void setPath(String path) {
      this.path = path;
    }

    public long getTimestamp() {
      return this.timestamp;
    }

    public boolean isSuccess() {
      return success;
    }

    public void setSuccess(boolean success) {
      this.success = success;
    }

    public ResultMessage(int code, String message) {
      super();
      this.code = code;
      this.message = message;
    }

    public String getMessage() {
      return message;
    }

    public int getCode() {
      return code;
    }

    @Override
    public String toString() {
      return "ResultMessage [message=" + message + ", code=" + code + ", path=" + path
          + ", timestamp=" + timestamp + ", success=" + success + "]";
    }

  }

}
